<

iOS アプリに Flutter 画面を追加する

このガイドでは、既存の iOS アプリに単一の Flutter 画面を追加する方法について説明します。

FlutterEngineとFlutterViewControllerを起動する

既存の iOS から Flutter 画面を起動するには、FlutterEngineそしてFlutterViewController

FlutterEngineあなたと同じ寿命かもしれないFlutterViewControllerあるいはあなたの長生きをするFlutterViewController

見るロードシーケンスとパフォーマンスレイテンシとメモリに関する詳細な分析については、 エンジンの予熱のトレードオフ。

FlutterEngineを作成する

を作成する場所FlutterEngineホストアプリによって異なります。

この例では、FlutterEngineSwiftUI 内のオブジェクトObservableObject。 次にこれを渡しますFlutterEngineContentViewを使用してenvironmentObject()財産。

MyApp.swift
import SwiftUI
 import Flutter
 // The following library connects plugins with iOS platform code to this app.
 import FlutterPluginRegistrant

 class FlutterDependencies: ObservableObject {
   let flutterEngine = FlutterEngine(name: "my flutter engine")
   init(){
     // Runs the default Dart entrypoint with a default Flutter route.
     flutterEngine.run()
     // Connects plugins with iOS platform code to this app.
     GeneratedPluginRegistrant.register(with: self.flutterEngine);
   }
 }

 @main
 struct MyApp: App {
   // flutterDependencies will be injected using EnvironmentObject.
   @StateObject var flutterDependencies = FlutterDependencies()
     var body: some Scene {
       WindowGroup {
         ContentView().environmentObject(flutterDependencies)
       }
     }
 }

例として、FlutterEngine、アプリの起動時にプロパティとして公開されます アプリのデリゲート。

AppDelegate.swift
import UIKit
import Flutter
// The following library connects plugins with iOS platform code to this app.
import FlutterPluginRegistrant

@UIApplicationMain
class AppDelegate: FlutterAppDelegate { // More on the FlutterAppDelegate.
  lazy var flutterEngine = FlutterEngine(name: "my flutter engine")

  override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Runs the default Dart entrypoint with a default Flutter route.
    flutterEngine.run();
    // Connects plugins with iOS platform code to this app.
    GeneratedPluginRegistrant.register(with: self.flutterEngine);
    return super.application(application, didFinishLaunchingWithOptions: launchOptions);
  }
}

この例では、FlutterEngineSwiftUI 内のオブジェクトObservableObject。 次にこれを渡しますFlutterEngineContentViewを使用してenvironmentObject()財産。

AppDelegate.h
@import UIKit;
@import Flutter;

@interface AppDelegate : FlutterAppDelegate // More on the FlutterAppDelegate below.
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
AppDelegate.m
// The following library connects plugins with iOS platform code to this app.
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>

#import "AppDelegate.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
  self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
  // Runs the default Dart entrypoint with a default Flutter route.
  [self.flutterEngine run];
  // Connects plugins with iOS platform code to this app.
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

FlutterEngine で FlutterViewController を表示する

次の例は、一般的なContentViewとともにButtonプレゼントに夢中FlutterViewController。 のFlutterViewControllerコンストラクターは事前にウォームアップされたものを受け取りますFlutterEngine議論として。FlutterEngine渡される としてEnvironmentObject経由flutterDependencies

ContentView.swift
import SwiftUI
import Flutter

struct ContentView: View {
  // Flutter dependencies are passed in an EnvironmentObject.
  @EnvironmentObject var flutterDependencies: FlutterDependencies

  // Button is created to call the showFlutter function when pressed.
  var body: some View {
    Button("Show Flutter!") {
      showFlutter()
    }
  }

func showFlutter() {
    // Get the RootViewController.
    guard
      let windowScene = UIApplication.shared.connectedScenes
        .first(where: { $0.activationState == .foregroundActive && $0 is UIWindowScene }) as? UIWindowScene,
      let window = windowScene.windows.first(where: \.isKeyWindow),
      let rootViewController = window.rootViewController
    else { return }

    // Create the FlutterViewController.
    let flutterViewController = FlutterViewController(
      engine: flutterDependencies.flutterEngine,
      nibName: nil,
      bundle: nil)
    flutterViewController.modalPresentationStyle = .overCurrentContext
    flutterViewController.isViewOpaque = false

    rootViewController.present(flutterViewController, animated: true)
  }
}

次の例は、一般的なViewControllerとともにUIButtonプレゼントに夢中FlutterViewController。 のFlutterViewControllerを使用しますFlutterEngine実例 で作成されたAppDelegate

ViewController.swift
import UIKit
import Flutter

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    // Make a button to call the showFlutter function when pressed.
    let button = UIButton(type:UIButton.ButtonType.custom)
    button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
    button.setTitle("Show Flutter!", for: UIControl.State.normal)
    button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
    button.backgroundColor = UIColor.blue
    self.view.addSubview(button)
  }

  @objc func showFlutter() {
    let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
    let flutterViewController =
        FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
    present(flutterViewController, animated: true, completion: nil)
  }
}

次の例は、一般的なViewControllerとともにUIButtonプレゼントに夢中FlutterViewController。 のFlutterViewControllerを使用しますFlutterEngine実例 で作成されたAppDelegate

ViewController.m
@import Flutter;
#import "AppDelegate.h"
#import "ViewController.h"

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    // Make a button to call the showFlutter function when pressed.
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self
               action:@selector(showFlutter)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
    button.backgroundColor = UIColor.blueColor;
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [self.view addSubview:button];
}

- (void)showFlutter {
    FlutterEngine *flutterEngine =
        ((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
    FlutterViewController *flutterViewController =
        [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:YES completion:nil];
}
@end

これで、iOS アプリに Flutter 画面が埋め込まれました。

あるいは- 暗黙的な FlutterEngine を使用して FlutterViewController を作成する

前の例の代わりに、次のようにすることもできます。FlutterViewController暗黙的に独自のものを作成するFlutterEngineそれなし 事前に予熱を行ってください。

これは通常は推奨されません。FlutterEngineオンデマンドは顕著な問題を引き起こす可能性があります までの待ち時間FlutterViewControllerは 表示され、最初のフレームをレンダリングするとき。ただし、これは次の可能性があります Flutter 画面がめったに表示されない場合や、適切な機能がない場合に便利です。 Dart VM をいつ開始する必要があるか、および Flutter をいつ開始するかを決定するためのヒューリスティック ビューコントローラー間で状態を保持する必要はありません。

させるにはFlutterViewController存在せずに存在するFlutterEngineを省略します。FlutterEngineを構築し、FlutterViewControllerエンジンリファレンスなし。

import SwiftUI
import Flutter

struct ContentView: View {
  var body: some View {
    Button("Show Flutter!") {
      openFlutterApp()
    }
  }

func openFlutterApp() {
    // Get the RootViewController.
    guard
      let windowScene = UIApplication.shared.connectedScenes
        .first(where: { $0.activationState == .foregroundActive && $0 is UIWindowScene }) as? UIWindowScene,
      let window = windowScene.windows.first(where: \.isKeyWindow),
      let rootViewController = window.rootViewController
    else { return }

    // Create the FlutterViewController without an existing FlutterEngine.
    let flutterViewController = FlutterViewController(
      project: nil,
      nibName: nil,
      bundle: nil)
    flutterViewController.modalPresentationStyle = .overCurrentContext
    flutterViewController.isViewOpaque = false

    rootViewController.present(flutterViewController, animated: true)
  }
}
ViewController.swift
// Existing code omitted.
func showFlutter() {
  let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
  present(flutterViewController, animated: true, completion: nil)
}
ViewController.m
// Existing code omitted.
- (void)showFlutter {
  FlutterViewController *flutterViewController =
      [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
  [self presentViewController:flutterViewController animated:YES completion:nil];
}
@end

見るロードシーケンスとパフォーマンスレイテンシとメモリ使用量について詳しく調べてください。

FlutterAppDelegate の使用

アプリケーションのUIApplicationDelegateサブクラスFlutterAppDelegate推奨されていますが、必須ではありません。

FlutterAppDelegate次のような機能を実行します。

  • 次のようなアプリケーション コールバックの転送openURLなどのプラグインにローカル認証。
  • Flutter接続を開いたままにする 電話画面がロックされているときはデバッグモードで。

FlutterAppDelegate サブクラスの作成

のサブクラスの作成FlutterAppDelegateUIKitアプリで表示されました の中にFlutterEngine と FlutterViewController セクションを開始する。 SwiftUI アプリでは、FlutterAppDelegateに準拠するものObservableObject次のようなプロトコル:

import SwiftUI
import Flutter
import FlutterPluginRegistrant

class AppDelegate: FlutterAppDelegate, ObservableObject {
  let flutterEngine = FlutterEngine(name: "my flutter engine")

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
      // Runs the default Dart entrypoint with a default Flutter route.
      flutterEngine.run();
      // Used to connect plugins (only if you have plugins with iOS platform code).
      GeneratedPluginRegistrant.register(with: self.flutterEngine);
      return true;
    }
}

@main
struct MyApp: App {
//  Use this property wrapper to tell SwiftUI
//  it should use the AppDelegate class for the application delegate
  @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

  var body: some Scene {
      WindowGroup {
        ContentView()
      }
  }
}

次に、あなたの見解では、AppDelegateとしてアクセス可能ですEnvironmentObject

import SwiftUI
import Flutter

struct ContentView: View {
  // Access the AppDelegate using an EnvironmentObject.
  @EnvironmentObject var appDelegate: AppDelegate

  var body: some View {
    Button("Show Flutter!") {
      openFlutterApp()
    }
  }

func openFlutterApp() {
    // Get the RootViewController.
    guard
      let windowScene = UIApplication.shared.connectedScenes
        .first(where: { $0.activationState == .foregroundActive && $0 is UIWindowScene }) as? UIWindowScene,
      let window = windowScene.windows.first(where: \.isKeyWindow),
      let rootViewController = window.rootViewController
    else { return }

    // Create the FlutterViewController.
    let flutterViewController = FlutterViewController(
      // Access the Flutter Engine via AppDelegate.
      engine: appDelegate.flutterEngine,
      nibName: nil,
      bundle: nil)
    flutterViewController.modalPresentationStyle = .overCurrentContext
    flutterViewController.isViewOpaque = false

    rootViewController.present(flutterViewController, animated: true)
  }
}

FlutterAppDelegateを直接サブクラスにできない場合

アプリのデリゲートが直接作成できない場合は、FlutterAppDelegateサブクラス、 アプリのデリゲートにFlutterAppLifeCycleProviderプラグインが必要なコールバックを確実に受信できるようにするためのプロトコルです。 そうしないと、これらのイベントに依存するプラグインの動作が未定義になる可能性があります。

例えば:

AppDelegate.swift
import Foundation
import Flutter

class AppDelegate: UIResponder, UIApplicationDelegate, FlutterAppLifeCycleProvider, ObservableObject {

  private let lifecycleDelegate = FlutterPluginAppLifeCycleDelegate()

  let flutterEngine = FlutterEngine(name: "flutter_nps_engine")

  override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    flutterEngine.run()
    return lifecycleDelegate.application(application, didFinishLaunchingWithOptions: launchOptions ?? [:])
  }

  func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    lifecycleDelegate.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
  }

  func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    lifecycleDelegate.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
  }

  func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    lifecycleDelegate.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)
  }

  func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    return lifecycleDelegate.application(app, open: url, options: options)
  }

  func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
    return lifecycleDelegate.application(application, handleOpen: url)
  }

  func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
    return lifecycleDelegate.application(application, open: url, sourceApplication: sourceApplication ?? "", annotation: annotation)
  }

  func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
    lifecycleDelegate.application(application, performActionFor: shortcutItem, completionHandler: completionHandler)
  }

  func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    lifecycleDelegate.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
  }

  func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    lifecycleDelegate.application(application, performFetchWithCompletionHandler: completionHandler)
  }

  func add(_ delegate: FlutterApplicationLifeCycleDelegate) {
    lifecycleDelegate.add(delegate)
  }
}
AppDelegate.h
@import Flutter;
@import UIKit;
@import FlutterPluginRegistrant;

@interface AppDelegate : UIResponder <UIApplicationDelegate, FlutterAppLifeCycleProvider>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end

実装は主に a に委任する必要があります。FlutterPluginAppLifeCycleDelegate:

AppDelegate.m
@interface AppDelegate ()
@property (nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate;
@end

@implementation AppDelegate

- (instancetype)init {
    if (self = [super init]) {
        _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
    }
    return self;
}

- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id>*))launchOptions {
    self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
    [self.flutterEngine runWithEntrypoint:nil];
    [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
    return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}

// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {
    UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
    if ([viewController isKindOfClass:[FlutterViewController class]]) {
        return (FlutterViewController*)viewController;
    }
    return nil;
}

- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
    [_lifeCycleDelegate application:application
didRegisterUserNotificationSettings:notificationSettings];
}

- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    [_lifeCycleDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
    [_lifeCycleDelegate application:application
       didReceiveRemoteNotification:userInfo
             fetchCompletionHandler:completionHandler];
}

- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
    return [_lifeCycleDelegate application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
    return [_lifeCycleDelegate application:application handleOpenURL:url];
}

- (BOOL)application:(UIApplication*)application
            openURL:(NSURL*)url
  sourceApplication:(NSString*)sourceApplication
         annotation:(id)annotation {
    return [_lifeCycleDelegate application:application
                                   openURL:url
                         sourceApplication:sourceApplication
                                annotation:annotation];
}

- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
  completionHandler:(void (^)(BOOL succeeded))completionHandler {
    [_lifeCycleDelegate application:application
       performActionForShortcutItem:shortcutItem
                  completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
  completionHandler:(nonnull void (^)(void))completionHandler {
    [_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifier
                  completionHandler:completionHandler];
}

- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
    [_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}

- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {
    [_lifeCycleDelegate addDelegate:delegate];
}
@end

起動オプション

この例では、デフォルトの起動設定を使用して Flutter を実行する方法を示します。

Flutter ランタイムをカスタマイズするには、 Dart エントリポイント、ライブラリ、ルートを指定することもできます。

ダーツエントリーポイント

電話をかけるrunFlutterEngine、デフォルトでは、 を実行しますmain()ダーツ機能 あなたのlib/main.dartファイル。

次を使用して、別のエントリポイント関数を実行することもできます。runWithEntrypointNSString指定する 別の Dart 機能。

ダーツライブラリー

Dart 関数の指定に加えて、エントリポイントも指定できます。 特定のファイル内の関数。

たとえば、次の実行myOtherEntrypoint()lib/other_file.dartそれ以外のmain()lib/main.dart:

flutterEngine.run(withEntrypoint: "myOtherEntrypoint", libraryURI: "other_file.dart")
[flutterEngine runWithEntrypoint:@"myOtherEntrypoint" libraryURI:@"other_file.dart"];

ルート

Flutter バージョン 1.22 以降、Flutter の初期ルートを設定できるようになりましたWidgetsAppFlutterEngine または FlutterViewController。

let flutterEngine = FlutterEngine()
// FlutterDefaultDartEntrypoint is the same as nil, which will run main().
engine.run(
  withEntrypoint: "main", initialRoute: "/onboarding")
FlutterEngine *flutterEngine = [[FlutterEngine alloc] init];
// FlutterDefaultDartEntrypoint is the same as nil, which will run main().
[flutterEngine runWithEntrypoint:FlutterDefaultDartEntrypoint
                    initialRoute:@"/onboarding"];

このコードは、dart:uiwindow.defaultRouteName"/onboarding"それ以外の"/"

あるいは、事前ウォーミングを行わずに FlutterViewController を直接構築することもできます。 flutterエンジン:

let flutterViewController = FlutterViewController(
      project: nil, initialRoute: "/onboarding", nibName: nil, bundle: nil)
FlutterViewController* flutterViewController =
      [[FlutterViewController alloc] initWithProject:nil
                                        initialRoute:@"/onboarding"
                                             nibName:nil
                                              bundle:nil];

見るナビゲーションとルーティングFlutter のルートについて詳しくは、こちらをご覧ください。

他の

前の例は、カスタマイズする方法をいくつか示しているだけです Flutter インスタンスがどのように開始されるか。使用するプラットフォームチャネル、 データをプッシュしたり、Flutter 環境を準備したりするのは自由です Flutter UI を表示する前に、任意の方法で、FlutterViewController